 
func fetchUserAttr() async -> [String:String] {
    var userAttr: [String:String] = [:]
    do {
        let curAuthMsg = try await Amplify.Auth.fetchUserAttributes()
        for attr in curAuthMsg {
            userAttr[attr.key.rawValue] = attr.value
        }
    } catch {
        print("Failed to fetch user attributes")
    }
    return userAttr
}
Amplify.Auth.fetchUserAttributes() 拿回來的是一個 Array,裡面由 Amplify.AuthUserAttributes 所構成key.rawValue
import { Hub, Logger } from 'aws-amplify';
const logger = new Logger('My-Logger');
const listener = (data) => {
  switch (data?.payload?.event) {
    case 'configured':
      logger.info('the Auth module is configured');
      break;
    case 'signIn':
      logger.info('user signed in');
      break;
    case 'signIn_failure':
      logger.error('user sign in failed');
      break;
    case 'signUp':
      logger.info('user signed up');
      break;
    case 'signUp_failure':
      logger.error('user sign up failed');
      break;
    case 'confirmSignUp':
      logger.info('user confirmation successful');
      break;
    case 'completeNewPassword_failure':
      logger.error('user did not complete new password flow');
      break;
    case 'autoSignIn':
      logger.info('auto sign in successful');
      break;
    case 'autoSignIn_failure':
      logger.error('auto sign in failed');
      break;
    case 'forgotPassword':
      logger.info('password recovery initiated');
      break;
    case 'forgotPassword_failure':
      logger.error('password recovery failed');
      break;
    case 'forgotPasswordSubmit':
      logger.info('password confirmation successful');
      break;
    case 'forgotPasswordSubmit_failure':
      logger.error('password confirmation failed');
      break;
    case 'verify':
      logger.info('TOTP token verification successful');
      break;
    case 'tokenRefresh':
      logger.info('token refresh succeeded');
      break;
    case 'tokenRefresh_failure':
      logger.error('token refresh failed');
      break;
    case 'cognitoHostedUI':
      logger.info('Cognito Hosted UI sign in successful');
      break;
    case 'cognitoHostedUI_failure':
      logger.error('Cognito Hosted UI sign in failed');
      break;
    case 'customOAuthState':
      logger.info('custom state returned from CognitoHosted UI');
      break;
    case 'customState_failure':
      logger.error('custom state failure');
      break;
    case 'parsingCallbackUrl':
      logger.info('Cognito Hosted UI OAuth url parsing initiated');
      break;
    case 'userDeleted':
      logger.info('user deletion successful');
      break;
    case 'updateUserAttributes':
      logger.info('user attributes update successful');
      break;
    case 'updateUserAttributes_failure':
      logger.info('user attributes update failed');
      break;
    case 'signOut':
      logger.info('user signed out');
      break;
    default:
      logger.info('unknown event type');
      break;
  }
};
Hub.listen('auth', listener);
func signOutLocally() async {
    let result = await Amplify.Auth.signOut()
    guard let signOutResult = result as? AWSCognitoSignOutResult
    else {
        print("Signout failed")
        return
    }
    // Return Login View
    print("Log out ~~")
    self.isSignOut = true
    print("Local signout successful: \(signOutResult.signedOutLocally)")
    switch signOutResult {
    case .complete:
        // Sign Out completed fully and without errors.
        print("Signed out successfully")
    case let .partial(revokeTokenError, globalSignOutError, hostedUIError):
        // Sign Out completed with some errors. User is signed out of the device.
        if let hostedUIError = hostedUIError {
            print("HostedUI error  \(String(describing: hostedUIError))")
        }
        if let globalSignOutError = globalSignOutError {
            // Optional: Use escape hatch to retry revocation of globalSignOutError.accessToken.
            print("GlobalSignOut error  \(String(describing: globalSignOutError))")
        }
        if let revokeTokenError = revokeTokenError {
            // Optional: Use escape hatch to retry revocation of revokeTokenError.accessToken.
            print("Revoke token error  \(String(describing: revokeTokenError))")
        }
    case .failed(let error):
        // Sign Out failed with an exception, leaving the user signed in.
        print("SignOut failed with \(error)")
    }
}
func uploadStamp() async {
    print("Sync local pic -> Amplify S3")
    let attrs = await fetchUserAttr()
    let username = attrs["name"] ?? "guest"
    for picUrl in uploadFilenameQueue {
        let fileNameKey = picUrl.lastPathComponent
        print("Upload -- \(fileNameKey)")
        let uploadTask = Amplify.Storage.uploadFile(
            key: "\(username)/\(fileNameKey)",
            local: picUrl
        )
        //Task {
        //    for await progress in await uploadTask.progress {
        //        print("Progress: \(progress)")
        //    }
        //}
        do {
            let data = try await uploadTask.value
            print("Completed: \(data)")
        } catch {
            print("Couldn't upload file: \(picUrl)")
        }
    }
    print("Clean queue")
    uploadFilenameQueue.removeAll()
}
 
 
